package nachos.threads;

import nachos.machine.*;

/**
 * A <tt>Semaphore</tt> is a synchronization primitive with an unsigned value.
 * A semaphore has only two operations:
 *
 * <ul>
 * <li><tt>P()</tt>: waits until the semaphore's value is greater than zero,
 * then decrements it.
 * <li><tt>V()</tt>: increments the semaphore's value, and wakes up one thread
 * waiting in <tt>P()</tt> if possible.
 * </ul>
 *
 * <p>
 * Note that this API does not allow a thread to read the value of the
 * semaphore directly. Even if you did read the value, the only thing you would
 * know is what the value used to be. You don't know what the value is now,
 * because by the time you get the value, a context switch might have occurred,
 * and some other thread might have called <tt>P()</tt> or <tt>V()</tt>, so the
 * true value might now be different.
 */
public class Semaphore {
    /**
     * Allocate a new semaphore.
     *
     * @param initialValue the initial value of this semaphore.
     */
    public Semaphore(int initialValue) {
        value = initialValue;
    }

    /**
     * Atomically wait for this semaphore to become non-zero and decrement it.
     */
    public void P() {
        boolean intStatus = Machine.interrupt().disable();

        if (value == 0) {
            waitQueue.waitForAccess(KThread.currentThread());
            KThread.sleep();
        } else {
            value--;
        }

        Machine.interrupt().restore(intStatus);
    }

    /**
     * Atomically increment this semaphore and wake up at most one other thread
     * sleeping on this semaphore.
     * Notice the guarantee that at most one other thread would be waken up.
     */
    public void V() {
        boolean intStatus = Machine.interrupt().disable();

        KThread thread = waitQueue.nextThread();
        if (thread != null) {
            thread.ready();
        } else {
            value++;
        }

        Machine.interrupt().restore(intStatus);
    }

    private static class PingTest implements Runnable {
        PingTest(Semaphore ping, Semaphore pong) {
            this.ping = ping;
            this.pong = pong;
        }

        public void run() {
            System.out.println("to Semaphore PingTest");
            for (int i = 0; i < 10; i++) {
                System.out.println("currentThread : " + Thread.currentThread().getName() +" loop " +i);
                ping.P();
                pong.V();
            }
        }

        private final Semaphore ping;
        private final Semaphore pong;
    }

    /**
     * Test if this module is working.
     */
    public static void selfTest() {
        System.out.println("\nto SemaphoreTest here\n");

        Semaphore ping = new Semaphore(0);
        Semaphore pong = new Semaphore(0);

        new KThread(new PingTest(ping, pong)).setName("ping").fork();

        for (int i = 0; i < 10; i++) {
            System.out.println("currentThread : " + Thread.currentThread().getName() +" loop " +i);
            ping.V();
            pong.P();
        }
    }

    private int value;
    // 这个 waitingSet 含义是存储那些因为信号量而等待的内核线程
    // 一个信号量实例对应一个 waitingSet
    private final ThreadQueue waitQueue = ThreadedKernel.scheduler.newThreadQueue(false);
}
